'''
Created on 11 juin 2013

@author: Nils Amiet
'''

from ITInfluence.polarity import PolarityCounter
from ITInfluence.models import Tweet, User, Friendship
from ITInfluence.hashtags import getAllHashtags
from InfrarougeTwitterInfluence import settings
import sqlite3
import math

infrarougeDatabasePath = settings.DATABASES["infrarouge"]["NAME"]

def getUsers():
    getUsers = []
    
    with sqlite3.connect(infrarougeDatabasePath) as connection:
        cursor = connection.cursor()
        
        query = "SELECT * FROM user"
        cursor.execute(query)
        
        for user in cursor:
            getUsers += [user]
        
        cursor.close()
        
    return getUsers

def getForums():
    forums = []
    
    with sqlite3.connect(infrarougeDatabasePath) as connection:
        cursor = connection.cursor()
        
        query = """
        SELECT *
        FROM forum
        """
        
        cursor.execute(query)
        for forum in cursor:
            builtForum = {
                          "id": forum[0],
                          "title": forum[1],
                          "description": forum[2],
                          }
            forums += [builtForum]
            
    return forums

def polarityReplies(forum=None):
    '''Returns a list of replies containing the source, destination, polarity, timestamp and forum id'''
    replies = []
    
    with sqlite3.connect(infrarougeDatabasePath) as connection:
        cursor = connection.cursor()
        
        query = """
        SELECT r.fkfrom, r.fkto, m.polarity, m.timestamp, f.id
        FROM reply as r
        INNER JOIN threadmessage as m
            ON r.fkthreadmessage=m.id
        INNER JOIN forumthread as t
            ON m.fkforumthread=t.id
        INNER JOIN forum as f
            ON t.fkparentforum=f.id
        """
        
        if forum is not None:
            query += "WHERE f.id=" + str(forum)
        cursor.execute(query)
        
        for reply in cursor:
            builtReply = {
                          "from": reply[0],
                          "to": reply[1],
                          "polarity": reply[2],
                          "timestamp": reply[3],
                          "forum": reply[4]
                          }
            
            replies  += [builtReply]
        
        cursor.close()
        
    return replies

def polarizationForAllForums(equalTime=True, equalRepliesCount=True):
    forums = getForums()
    
    resultsEqualTime = []
    resultsEqualRepliesCount = []
    
    for forum in forums:
        forumID = forum["id"]
        title = "Forum " + str(forumID)
        replies = polarityReplies(forum=forumID)
        timeSortedReplies = sorted(replies, key=lambda x: x["timestamp"])
        
        sliceCount = 20
        
        # equal time interval
        if equalTime:
            res = computePolarizationOverTime(timeSortedReplies, forumID, sliceCount)
            resultsEqualTime += [(title, res)]
        
        # equal replies count interval
        if equalRepliesCount:
            res2 = computePolarizationOverTimeSamePostCount(timeSortedReplies, forumID, sliceCount)
            resultsEqualRepliesCount += [(title, res2)]
        
    return resultsEqualTime, resultsEqualRepliesCount

def computePolarizationOverTimeSamePostCount(replies, forumID, sliceCount):
    repliesCount = len(replies)
    
    deltaRepliesFloat = repliesCount / float(sliceCount)
    deltaReplies = int(math.ceil(repliesCount / sliceCount))
    
    if deltaRepliesFloat is not deltaReplies:
        deltaReplies += 1
        
    ndiVariation = []
    replyOffset = 0
    while replyOffset < repliesCount:
        upperOffset = replyOffset + deltaReplies + 1
        r = replies[replyOffset:upperOffset]
        polarityCounter = PolarityCounter(r)
        edgesCount = len(polarityCounter.edges)
        ndiVariation += [(int(replyOffset), polarityCounter.NDI, edgesCount)]
        
        replyOffset += deltaReplies
        
    times = [x[0] for x in ndiVariation]
    times = [i for i, unused in enumerate(times)]
    ndis = [x[1] for x in ndiVariation]

    return times, ndis
        
def computePolarizationOverTime(replies, forumID, sliceCount):
    first = replies[0]["timestamp"]
    last = replies[-1]["timestamp"]
    interval = last - first
    deltaTimeMillis = interval / sliceCount
    
    ndiVariation = []
    timeThreshold = first + deltaTimeMillis
    while timeThreshold <= last:
        lowerBound = timeThreshold - deltaTimeMillis
        upperBound = timeThreshold
        r = repliesInTimeInterval(replies, lowerBound, upperBound)
        polarityCounter = PolarityCounter(r)
        ndiVariation += [(int(timeThreshold), polarityCounter.NDI)]
        
        timeThreshold += deltaTimeMillis
        
    times = [x[0] for x in ndiVariation]
    times = [i for i, unused in enumerate(times)]
    ndis = [x[1] for x in ndiVariation]

    return times, ndis

def repliesInTimeInterval(replies, lowerBound, upperBound):
    return [reply for reply in replies if reply["timestamp"] >= lowerBound and reply["timestamp"] <= upperBound ]










'''Twitter'''
def getNDIForMostFrequentHashtags(equalTime=True, equalRepliesCount=True):
    hashtags = mostFrequentHashtags()
    print("Most frequent hashtags:")
    print(hashtags)
    resultsEqualTimeIntervals = []
    resultsEqualRepliesCountIntervals = []
    sliceCount = 20
    # Adjust this value as you wish
    # Greater value = More accurate results but more computation time
    maxFollowersCount = 50
    
    for hashtag in hashtags:
        eqTime, eqReplyCount = getNDIForHashtag(hashtag, sliceCount=sliceCount, maxFollowersCount=maxFollowersCount, equalTime=equalTime, equalRepliesCount=equalRepliesCount)
        resultsEqualTimeIntervals += eqTime
        resultsEqualRepliesCountIntervals += eqReplyCount
    
    return resultsEqualTimeIntervals, resultsEqualRepliesCountIntervals

def getNDIForHashtag(hashtag, sliceCount=20, maxFollowersCount=100, equalTime=True, equalRepliesCount=True):
    resultsEqualTimeIntervals = []
    resultsEqualRepliesCountIntervals = []
    
    print("Computing NDIs for hashtag %s..." % hashtag)
    ### Uncomment one of the following lines "tweets = ..."
    # Users with at most x followers, fast computation
    tweets = Tweet.objects.all().filter(user__followers_count__lte=maxFollowersCount).filter(hashtags__icontains=hashtag)
    # Complete dataset: Requires a lot of memory and a fast CPU for large networks
#     tweets = Tweet.objects.all().filter(hashtags__icontains=hashtag)
    timeSortedTweets = sorted(tweets, key=lambda x: x.created_at)
    
    if equalTime:
        ndis = getNDIValuesForHashtag(hashtag, sliceCount, timeSortedTweets)
        resultsEqualTimeIntervals += [("#" + hashtag, ndis)]
    if equalRepliesCount:
        ndis = getNDIValuesEqualRepliesCountForHashtag(hashtag, sliceCount, timeSortedTweets)
        resultsEqualRepliesCountIntervals += [("#" + hashtag, ndis)]
    
    return resultsEqualTimeIntervals, resultsEqualRepliesCountIntervals

def mostFrequentHashtags():
    hashtagCount = 6 # n most frequent
    hashtags = getAllHashtags()
    hashtags = sorted(hashtags, key=lambda x: x[1], reverse=True) # sort by number of occurences
    hashtags = [tag.lower() for tag, unused in hashtags]
    hashtags = convertListToSetKeepingOrder(hashtags)
    hashtags = hashtags[:hashtagCount]
    
    return hashtags

def convertListToSetKeepingOrder(xs):
    # Inspired from http://stackoverflow.com/a/480227
    seen = set()
    addToSet = seen.add
    return [x for x in xs if x not in seen and not addToSet(x)]

def computeReplies(tweets):
    replies = []
    
    for tweet in tweets:
        authorId = int(tweet.user.id)
        polarity = float(tweet.polarity)
        replyTo = int(tweet.in_reply_to_status_id)
        
        if replyTo is not 0: # reply to single person
            try:
                user = User.objects.get(id=replyTo)
                userId = int(user.id)
                builtReply = {
                              "from": authorId,
                              "to": userId,
                              "polarity": polarity,
                              }
                replies += [builtReply]
            except:
                # reply to all his/her followers
                replies += getFollowerReplies(authorId, polarity)
            
        else:
            # reply to all his/her followers
            replies += getFollowerReplies(authorId, polarity)
    
    return replies

def getFollowerReplies(authorId, polarity):
    replies = []
    friendships = Friendship.objects.all().filter(followed_user__id=authorId)
    followers = [int(f.user.id) for f in friendships]
    
    for follower in followers:
        builtReply = {
                  "from": authorId,
                  "to": follower,
                  "polarity": polarity,
                  }
        
        replies += [builtReply]
    return replies

def getNDIValuesEqualRepliesCountForHashtag(hashtag, sliceCount, timeSortedTweets):
    ndiVariation = []
    
    if len(timeSortedTweets) >= 2:
        tweetCount = len(timeSortedTweets)
        deltaTweetsFloat = tweetCount / float(sliceCount)
        deltaTweets = int(math.ceil(tweetCount / sliceCount))
        
        if deltaTweetsFloat is not deltaTweets:
            deltaTweets += 1
        
        tweetOffset = 0
        sliceIndex = 1
        while tweetOffset < tweetCount:
            print("Computing slice %s/%s" %(sliceIndex, sliceCount))
            upperOffset = tweetOffset + deltaTweets + 1
            tweetsSubset = timeSortedTweets[tweetOffset:upperOffset]
            replies = computeReplies(tweetsSubset)
            polarityCounter = PolarityCounter(replies)
            ndiVariation += [(int(tweetOffset), polarityCounter.NDI)]
            
            tweetOffset += deltaTweets
            sliceIndex += 1
        
    times = [x[0] for x in ndiVariation]
    times = [i for i, unused in enumerate(times)]
    ndis = [x[1] for x in ndiVariation]

    return times, ndis

def getNDIValuesForHashtag(hashtag, sliceCount, timeSortedTweets):
    ndiVariation = []
    
    if len(timeSortedTweets) >= 2:
        first = timeSortedTweets[0].created_at
        last = timeSortedTweets[-1].created_at
        interval = last - first
        deltaTimeMillis = interval / sliceCount
        
        
        sliceIndex = 1
        timeUpperBound = first + deltaTimeMillis
        while timeUpperBound <= last:
            print("Computing slice %s/%s" %(sliceIndex, sliceCount))
            tweetsSubset = tweetsInInterval(timeSortedTweets, timeUpperBound - deltaTimeMillis, timeUpperBound)
            replies = computeReplies(tweetsSubset)
            polarityCounter = PolarityCounter(replies)
            ndiVariation += [(timeUpperBound, polarityCounter.NDI)]
            
            timeUpperBound += deltaTimeMillis
            if timeUpperBound == timeUpperBound + deltaTimeMillis:
                break
            sliceIndex += 1
        
    times = [x[0] for x in ndiVariation]
    times = [i for i, unused in enumerate(times)]
    ndis = [x[1] for x in ndiVariation]

    return times, ndis

def tweetsInInterval(tweets, timeLowerBound, timeUpperBound):
    return [tweet for tweet in tweets if tweet.created_at >= timeLowerBound and tweet.created_at <= timeUpperBound]